﻿using System.Reflection;

using ThingsGateway.NewLife.Log;
using ThingsGateway.NewLife.Reflection;

namespace ThingsGateway.NewLife.Model;

/// <summary>通用插件接口</summary>
/// <remarks>
/// 为了方便构建一个简单通用的插件系统，先规定如下：
/// 1，负责加载插件的宿主，在加载插件后会进行插件实例化，此时可在插件构造函数中做一些事情，但不应该开始业务处理，因为宿主的准备工作可能尚未完成
/// 2，宿主一切准备就绪后，会顺序调用插件的Init方法，并将宿主标识传入，插件通过标识区分是否自己的目标宿主。插件的Init应尽快完成。
/// 3，如果插件实现了<see cref="IDisposable"/>接口，宿主最后会清理资源。
/// </remarks>
public interface IPlugin
{
    /// <summary>初始化</summary>
    /// <param name="identity">插件宿主标识</param>
    /// <param name="provider">服务提供者</param>
    /// <returns>返回初始化是否成功。如果当前宿主不是所期待的宿主，这里返回false</returns>
    Boolean Init(String? identity, IServiceProvider provider);
}

/// <summary>插件特性。用于判断某个插件实现类是否支持某个宿主</summary>
/// <remarks>实例化</remarks>
/// <param name="identity"></param>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class PluginAttribute(String identity) : Attribute
{
    /// <summary>插件宿主标识</summary>
    public String Identity { get; set; } = identity;
}

/// <summary>插件管理器</summary>
public class PluginManager : DisposeBase, IServiceProvider
{
    #region 属性
    /// <summary>宿主标识，用于供插件区分不同宿主</summary>
    public String? Identity { get; set; }

    /// <summary>宿主服务提供者</summary>
    public IServiceProvider? Provider { get; set; }

    /// <summary>插件集合</summary>
    public IPlugin[]? Plugins { get; set; }

    /// <summary>日志提供者</summary>
    public ILog Log { get; set; } = XTrace.Log;
    #endregion

    #region 构造
    /// <summary>实例化一个插件管理器</summary>
    public PluginManager() { }

    /// <summary>子类重载实现资源释放逻辑时必须首先调用基类方法</summary>
    /// <param name="disposing">从Dispose调用（释放所有资源）还是析构函数调用（释放非托管资源）。
    /// 因为该方法只会被调用一次，所以该参数的意义不太大。</param>
    protected override void Dispose(Boolean disposing)
    {
        base.Dispose(disposing);

        if (disposing)
        {
            // 倒序销毁
            var ps = Plugins;
            if (ps != null)
            {
                for (var i = ps.Length - 1; i >= 0; i--)
                {
                    ps[i].TryDispose();
                }
                Plugins = null;
            }
        }
    }
    #endregion

    #region 方法
    /// <summary>加载插件。此时是加载所有插件，无法识别哪些是需要的</summary>
    public void Load()
    {
        var list = new List<IPlugin>();
        // 此时是加载所有插件，无法识别哪些是需要的
        foreach (var item in LoadPlugins())
        {
            if (item != null)
            {
                try
                {
                    // 插件类注册到容器中，方便后续获取
                    var container = Provider?.GetService<IObjectContainer>();
                    container?.TryAddTransient(item, item);

                    var obj = Provider?.GetService(item) ?? item.CreateInstance();
                    if (obj is IPlugin plugin) list.Add(plugin);
                }
                catch (Exception ex)
                {
                    Log?.Debug(String.Empty, ex);
                }
            }
        }
        Plugins = list.ToArray();
    }

    /// <summary>加载插件</summary>
    /// <returns></returns>
    public IEnumerable<Type> LoadPlugins()
    {
        // 此时是加载所有插件，无法识别哪些是需要的
        foreach (var item in AssemblyX.FindAllPlugins(typeof(IPlugin), true))
        {
            if (item != null)
            {
                // 如果有插件特性，并且所有特性都不支持当前宿主，则跳过
                var atts = item.GetCustomAttributes<PluginAttribute>(true);
                if (atts?.Any(a => a.Identity != Identity) == true) continue;

                yield return item;
            }
        }
    }

    /// <summary>开始初始化。初始化之后，不属于当前宿主的插件将会被过滤掉</summary>
    public void Init()
    {
        var ps = Plugins;
        if (ps == null || ps.Length <= 0) return;

        var list = new List<IPlugin>();
        foreach (var item in ps)
        {
            try
            {
                if (item.Init(Identity, this)) list.Add(item);
            }
            catch (Exception ex)
            {
                Log?.Debug(String.Empty, ex);
            }
        }

        Plugins = list.ToArray();
    }
    #endregion

    #region IServiceProvider 成员
    Object? IServiceProvider.GetService(Type serviceType)
    {
        if (serviceType == typeof(PluginManager)) return this;

        return Provider?.GetService(serviceType);
    }
    #endregion
}